In [ ]:
from bokeh.io import show, output_notebook
output_notebook()
In [ ]:
import pandas as pd
import numpy as np
import datetime
from bokeh.models import (
Plot, Line, ColumnDataSource, DataRange1d, Range1d,
LinearAxis, BasicTicker,
DatetimeAxis, DatetimeTicker, DatetimeTickFormatter,
BoxZoomTool, PanTool, ResetTool, WheelZoomTool, TapTool,
HoverTool, Grid, Quad, Rect, Circle, Callback
)
from bokeh import palettes
from bokeh.properties import value
from bokeh.io import vplot
In [ ]:
from app.utils import timelog_path
raw = pd.read_table(timelog_path, quotechar=' ', sep=': ', names=['timestamp', 'activity'], engine='python',)
# Set the column types
raw.timestamp = pd.to_datetime(raw.timestamp)
raw = raw.drop_duplicates()
### Build the times
raw['formatted_time'] = raw.timestamp.apply(lambda x: x.strftime("%I %p")) # Used for hover tools
raw['end'] = raw.timestamp
raw['start'] = raw['end'].shift(1)
raw['start'] = np.where(
raw['activity'] == 'start', # If the activity is start
raw['timestamp'], # Set the start to timestamp
raw['start'], # Else leave it as start
)
raw['delta'] = raw.end - raw.start
# Remove the non-work activities which all have ***
raw = raw[~raw.activity.str.contains('\*\*\*')]
# Boil down the categories to the main work categories
raw.activity = raw.activity.str.split(' ').str[0]
raw.head()
In [ ]:
# Build a dictionary of frames - one for each category
def make_dfs(raw):
activities = list(raw.activity.unique())
activities.remove('start')
start_df = raw[raw.activity == 'start']
nan_df = start_df.copy()
nan_df['delta'] = np.NaN
nan_df['activity'] = '_'
dfs = {}
for activity in activities:
activity_df = raw[raw.activity == activity]
# Add in the start rows with 0 deltas and do cumsum
activity_df = activity_df.append(start_df)
activity_df.sort('timestamp', inplace=True)
activity_df['cumsum'] = np.cumsum(activity_df.groupby(activity_df.timestamp.dt.date)['delta'])
activity_df['cumsum_hrs'] = activity_df['cumsum'].dt.seconds / (60 * 60)
# Add in the nan rows so bokeh can plobt
activity_df = activity_df.append(nan_df)
activity_df.sort(['timestamp', 'activity'], inplace=True)
dfs[activity] = ColumnDataSource(activity_df[['cumsum_hrs', 'timestamp']])
return dfs, activities
dfs, cats = make_dfs(raw)
In [ ]:
def make_plot(dfs, activities, x_range, plot_width=900):
plot = Plot(
x_range=x_range,
y_range=Range1d(0, 11),
background_fill='black',
border_fill='black',
outline_line_color=None,
plot_width=plot_width,
plot_height=200,
toolbar_location='left'
)
yticker = BasicTicker(min_interval=3)
close_ticker = DatetimeTicker(desired_num_ticks=8)
year_ticker = DatetimeTicker(desired_num_ticks=4)
year_ticks = DatetimeTickFormatter(
formats={
'years': ["%Y"],
'months': ["%Y"],
'days': ["%Y"],
'hours': ["%Y"]
}
)
close_ticks = DatetimeTickFormatter(
formats={
'years': ["%b"],
'months': ["%b"],
'days': ["%a %d %b"],
'hours': ["%I%p %d %b"]
}
)
axis_properties = dict(
major_label_text_color='white',
)
plot.add_layout(LinearAxis(ticker=yticker, **axis_properties), 'left')
plot.add_layout(DatetimeAxis(formatter=close_ticks, ticker=close_ticker, **axis_properties), 'below')
plot.add_layout(DatetimeAxis(formatter=year_ticks, ticker=year_ticker, **axis_properties), 'below')
plot.add_layout(Grid(dimension=1, ticker=yticker, grid_line_alpha=0.3))
palette = getattr(palettes, 'Spectral%s' % len(activities))
for i, activity in enumerate(activities):
source = dfs[activity]
line = Line(
line_color=palette[i],
line_join='round', line_cap='round', line_width=5, line_alpha=0.75,
x='timestamp', y='cumsum_hrs'
)
plot.add_glyph(source, line)
return plot
In [ ]:
# Some times
first_day = raw.loc[0, 'timestamp']
today = datetime.datetime.today()
one_week_ago = today - datetime.timedelta(weeks=1)
two_weeks_ago = today - datetime.timedelta(weeks=2)
one_week_forward = today + datetime.timedelta(weeks=1)
# The ranges
all_range = Range1d(start=first_day, end=today)
month_range = Range1d(start=two_weeks_ago, end=one_week_forward)
week_range = Range1d(start=one_week_ago, end=today)
In [ ]:
highlight = Quad(
left='start', right='end', bottom=0, top=12,
fill_color='white', line_color='white', fill_alpha=0.2,
)
lowlight = Quad(
left='start', right='end', bottom=0, top=12,
fill_color='black', line_color='white', fill_alpha=0.5,
)
In [ ]:
#from IPython.display import Javascript
#Javascript('../app/static/javascripts/moment.min.js')
# MOMENT is already in ipython notebook!!
In [ ]:
all_plot = make_plot(dfs, cats, all_range)
detail_selection_source = ColumnDataSource(
{'start': [all_range.start, month_range.end],
'end': [month_range.start, all_range.end]}
)
all_plot.add_glyph(detail_selection_source, lowlight)
In [ ]:
detail = make_plot(dfs, cats, month_range)
detail.add_tools(PanTool(dimensions=['width']))
detail.add_tools(WheelZoomTool(dimensions=['width']))
detail.add_tools(ResetTool())
week_selection_source = ColumnDataSource({'start': [week_range.start], 'end': [week_range.end]})
detail.add_glyph(week_selection_source, highlight)
detail_code = """
// Update the month selection box on the all_data plot when month pans
var detail_selection_data = detail_selection_source.get('data');
var detail_start = cb_obj.get('frame').get('x_range').get('start');
var detail_end = cb_obj.get('frame').get('x_range').get('end');
detail_selection_data['start'][1] = detail_end;
detail_selection_data['end'][0] = detail_start;
detail_selection_source.trigger('change');
// Always make sure the week highlight box on detail is visible and centered
var week_selection_data = week_selection_source.get('data');
var x = moment.duration(detail_end - detail_start).asWeeks() / 2
var start = moment(detail_start);
var week_end = start.add(x, 'weeks').format('x');
var week_start = start.add(1, 'weeks').format('x'); // Subtract 1 week because obj was updated
week_selection_data['start'] = [week_start];
week_selection_data['end'] = [week_end];
week_selection_source.trigger('change');
console.log('The week starts on ' + start.format());
"""
detail_xrange_callback = Callback(args={}, code=detail_code)
detail_xrange_callback.args['detail_selection_source'] = detail_selection_source
detail_xrange_callback.args['week_selection_source'] = week_selection_source
detail.x_range.callback = detail_xrange_callback
In [ ]:
show(vplot(all_plot, detail))
In [ ]: